package cn.flkj.lucia2a2.intermediate;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.codec.binary.Hex;
import org.hyperledger.fabric.protos.ledger.rwset.kvrwset.KvRwset;
import org.hyperledger.fabric.protos.peer.Query;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.hyperledger.fabric.sdk.exception.TransactionException;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * @author 州长在手 2020/7/6 下午2:36
 */
class IntermediateChannel {
    /** 当前将要访问的智能合约所属频道名称 */
    private String channelName; // myChannel
//    /** 事务等待时间以秒为单位 */
//    private int transactionWaitTime = 100000;
//    /** 部署等待时间以秒为单位 */
//    private int deployWatiTime = 120000;
    /** 中继组织节点 */
    //private IntermediateOrg org;
    private Channel channel;
    /** 州长在手 */
    private HFClient client;
    private User user;
    private List<IntermediateOrderer> orderers;
    private List<IntermediatePeer> peers;
    private boolean openTLS;
    private BlockListener blockListener;
    private ChaincodeEventListener chaincodeEventListener;
    private String  eventNames;
    //获取peer节点上实例化的链码信息
    List<Query.ChaincodeInfo> queryInstantiated(Peer peer) throws InvalidArgumentException, ProposalException {
        return   channel.queryInstantiatedChaincodes(peer);
    }

    void init(IntermediateOrg org) throws TransactionException, InvalidArgumentException {
        this.openTLS = org.openTLS();
        this.blockListener = org.getBlockListener();
        this.chaincodeEventListener = org.getChaincodeEventListener();
        this.eventNames = org.getEventNames();
        this.client = org.getClient();
        this.user = org.getUser(org.getUsername());
        this.orderers = org.getOrderers();
        this.peers = org.getPeers();
        this.eventNames = org.getEventNames();
        setChannel(client);
    }

    void init( HFClient client,
             User user,
             List<IntermediateOrderer> orderers,
             List<IntermediatePeer> peers,
             boolean openTLS,
             BlockListener blockListener,
             ChaincodeEventListener chaincodeEventListener,
             String  eventNames) throws TransactionException, InvalidArgumentException {
        this.openTLS =openTLS;
        this.blockListener = blockListener;
        this.chaincodeEventListener = chaincodeEventListener;
        this.eventNames =eventNames;
        this.client = client;
        this.user = user;
        this.orderers =orderers;
        this.peers = peers;
        this.eventNames = eventNames;
        setChannel(client);
    }


    private void setChannel(HFClient client) throws InvalidArgumentException, TransactionException {
        client.setUserContext(user);
        channel = client.newChannel(channelName);
        for (IntermediateOrderer orderer : orderers) {
            Properties ordererProperties = new Properties();
            if (openTLS) {
                File ordererCert = new File(orderer.getServerCrtPath());
                File ordererUserClientCert = new File(orderer.getClientCertPath());
                File ordererUserClientKey = new File(orderer.getClientKeyPath());
                if (!ordererCert.exists()) {
                    throw new RuntimeException(
                            String.format("Missing cert file for: %s. Could not find at location: %s", orderer.getOrdererName(), ordererCert.getAbsolutePath()));
                }
                ordererProperties.setProperty("pemFile", ordererCert.getAbsolutePath());
                ordererProperties.setProperty("clientCertFile", ordererUserClientCert.getAbsolutePath());
                ordererProperties.setProperty("clientKeyFile", ordererUserClientKey.getAbsolutePath());
            }
            ordererProperties.setProperty("hostnameOverride", orderer.getOrdererName());
            ordererProperties.setProperty("sslProvider", "openSSL");
            ordererProperties.setProperty("negotiationType", "TLS");
            // 设置keepAlive以避免在不活跃的http2连接上超时的例子。在5分钟内，需要对服务器端进行更改，以接受更快的ping速率。
            ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTime", new Object[]{5L, TimeUnit.MINUTES});
            ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTimeout", new Object[]{8L, TimeUnit.SECONDS});
            ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveWithoutCalls", new Object[] {true});
            channel.addOrderer(
                    client.newOrderer(orderer.getOrdererName(), orderer.getOrdererLocation(), ordererProperties));
        }

        for (IntermediatePeer peer : peers) {
            Properties peerProperties = new Properties();
            if (openTLS) {
                File peerCert = new File(peer.getServerCrtPath());
                File peerUserClientCert = new File(peer.getClientCertPath());
                File peerUserClientKey = new File(peer.getClientKeyPath());
                if (!peerCert.exists()) {
                    throw new RuntimeException(
                            String.format("Missing cert file for: %s. Could not find at location: %s", peer.getPeerName(), peerCert.getAbsolutePath()));
                }
                peerProperties.setProperty("pemFile", peerCert.getAbsolutePath());
                peerProperties.setProperty("clientCertFile", peerUserClientCert.getAbsolutePath());
                peerProperties.setProperty("clientKeyFile", peerUserClientKey.getAbsolutePath());
            }
            // ret.setProperty("trustServerCertificate", "true"); //testing
            // environment only NOT FOR PRODUCTION!
            peerProperties.setProperty("hostnameOverride", peer.getPeerName());
            peerProperties.setProperty("sslProvider", "openSSL");
            peerProperties.setProperty("negotiationType", "TLS");
            // 在grpc的NettyChannelBuilder上设置特定选项
            peerProperties.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 9000000);
            // 如果未加入频道，该方法执行加入。如果已加入频道，则执行下一行方面新增Peer
            // channel.joinPeer(client.newPeer(peers.get().get(i).getPeerName(), fabricOrg.getPeerLocation(peers.get().get(i).getPeerName()), peerProperties));
            channel.addPeer(client.newPeer(peer.getPeerName(),peer.getPeerLocation(), peerProperties));
//            if (null != org.getPeers().get(i).getPeerEventHubLocation() && !org.getPeers().get(i).getPeerEventHubLocation().isEmpty()) {
//                channel.addEventHub(client.newEventHub(org.getPeers().get(i).getPeerName(), org.getPeers().get(i).getPeerEventHubLocation(), peerProperties));
//
//            }
        }

        if (!channel.isInitialized()) {
            channel.initialize();
        }
        if (null != blockListener) {
            // channel.registerBlockListener(org.getBlockListener());
            channel.registerBlockListener(blockEvent -> {
                try {
                    blockListener.received(execBlockInfo(blockEvent));
                } catch (Exception e) {
                    e.printStackTrace();
                    blockListener.received(getFailFromString(e.getMessage()));
                }
            });
        }
        if (null != chaincodeEventListener) {
            String[] event2Names = eventNames.split(",");
            for (String eventName : event2Names) {
                channel.registerChaincodeEventListener(Pattern.compile(".*"), Pattern.compile(Pattern.quote(eventName)), (handle, blockEvent, chaincodeEvent) -> {
                    try {
                        chaincodeEventListener.received(handle, execBlockInfo(blockEvent), chaincodeEvent.getEventName(), chaincodeEvent.getChaincodeId(), chaincodeEvent.getTxId());
                    } catch (IOException | InvalidArgumentException e) {
                        e.printStackTrace();
                        chaincodeEventListener.received(handle, getFailFromString(e.getMessage()), chaincodeEvent.getEventName(), chaincodeEvent.getChaincodeId(), chaincodeEvent.getTxId());
                    }
                });
            }
        }
    }

    /** 获取Fabric Channel */
    Channel get() {
        return channel;
    }

    void setChannelName(String channelName) {
        this.channelName = channelName;
    }

    /**
     * Peer加入频道
     *
     * @param peer 中继节点信息
     */
    JSONObject joinPeer(IntermediatePeer peer) throws InvalidArgumentException, ProposalException {
        Properties peerProperties = new Properties();
        if (openTLS) {
            /*File peerCert = new File(org.getPeers().get(0).getServerCrtPath());*/
            File peerCert = new File("/root/fabric1.4/peer0.dev.flt.cn/tls/ca.crt");
            if (!peerCert.exists()) {
                throw new RuntimeException(
                        String.format("Missing cert file for: %s. Could not find at location: %s", peer.getPeerName(), peerCert.getAbsolutePath()));
            }
            // System.out.println("_____________________________________________"+peerCert.getAbsolutePath());
            peerProperties.setProperty("pemFile", peerCert.getAbsolutePath());
        }
        // ret.setProperty("trustServerCertificate", "true"); //testing
        // environment only NOT FOR PRODUCTION!
        peerProperties.setProperty("hostnameOverride", peer.getPeerName());
        peerProperties.setProperty("sslProvider", "openSSL");
        peerProperties.setProperty("negotiationType", "TLS");
        // 在grpc的NettyChannelBuilder上设置特定选项
        peerProperties.put("grpc.ManagedChannelBuilderOption.maxInboundMessageSize", 9000000);
        // 如果未加入频道，该方法执行加入。如果已加入频道，则执行下一行方面新增Peer
        // channel.joinPeer(client.newPeer(peers.get().get(i).getPeerName(), fabricOrg.getPeerLocation(peers.get().get(i).getPeerName()), peerProperties));
        Peer fabricPeer = client.newPeer(peer.getPeerName(), peer.getPeerLocation(), peerProperties);
        //      System.out.println("==========================================================================="+fabricPeer.toString());
        for (Peer peerNow : channel.getPeers()) {
            if (peerNow.getUrl().equals(fabricPeer.getUrl())) {
                return getFailFromString("peer has already in channel");
            }
        }
        channel.joinPeer(fabricPeer);
//        if (null != peer.getPeerEventHubLocation() && !peer.getPeerEventHubLocation().isEmpty()) {
//            channel.addEventHub(org.getClient().newEventHub(peer.getPeerName(), peer.getPeerEventHubLocation(), peerProperties));
//        }
        return getSuccessFromString();
    }

    private JSONObject getSuccess(JSON json) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", BlockListener.SUCCESS);
        jsonObject.put("data", json);
        return jsonObject;
    }

    private JSONObject getSuccessFromString() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", BlockListener.SUCCESS);
        jsonObject.put("data", JSON.parse("peer join channel success"));
        return jsonObject;
    }

    private JSONObject getFailFromString(String data) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", BlockListener.ERROR);
        jsonObject.put("data", data);
        return jsonObject;
    }

    /** 查询当前频道的链信息，包括链长度、当前最新区块hash以及当前最新区块的上一区块hash */
    JSONObject getBlockchainInfo() throws InvalidArgumentException, ProposalException {
        JSONObject blockchainInfo = new JSONObject();
        blockchainInfo.put("height", channel.queryBlockchainInfo().getHeight());
        blockchainInfo.put("currentBlockHash", Hex.encodeHexString(channel.queryBlockchainInfo().getCurrentBlockHash()));
        blockchainInfo.put("previousBlockHash", Hex.encodeHexString(channel.queryBlockchainInfo().getPreviousBlockHash()));
        return getSuccess(blockchainInfo);
    }

    /**
     * 在指定频道内根据transactionID查询区块
     *
     * @param txID transactionID
     */
    JSONObject queryBlockByTransactionID(String txID) throws InvalidArgumentException, ProposalException, IOException {
        return execBlockInfo(channel.queryBlockByTransactionID(txID));
    }

    /**
     * 在指定频道内根据hash查询区块
     *
     * @param blockHash hash
     */
    JSONObject queryBlockByHash(byte[] blockHash) throws InvalidArgumentException, ProposalException, IOException {
        return execBlockInfo(channel.queryBlockByHash(blockHash));
    }

    /**
     * 在指定频道内根据区块高度查询区块
     *
     * @param blockNumber 区块高度
     */
    JSONObject queryBlockByNumber(long blockNumber) throws InvalidArgumentException, ProposalException, IOException {
        return execBlockInfo(channel.queryBlockByNumber(blockNumber));
    }

    /**
     * 解析区块信息对象
     *
     * @param blockInfo 区块信息对象
     */
    private JSONObject execBlockInfo(BlockInfo blockInfo) throws IOException, InvalidArgumentException {
        final long blockNumber = blockInfo.getBlockNumber();
        JSONObject blockJson = new JSONObject();
        blockJson.put("blockNumber", blockNumber);
        blockJson.put("dataHash", Hex.encodeHexString(blockInfo.getDataHash()));
        blockJson.put("previousHashID", Hex.encodeHexString(blockInfo.getPreviousHash()));
        blockJson.put("calculatedBlockHash", Hex.encodeHexString(SDKUtils.calculateBlockHash(client, blockNumber, blockInfo.getPreviousHash(), blockInfo.getDataHash())));
        blockJson.put("envelopeCount", blockInfo.getEnvelopeCount());
        blockJson.put("envelopes", getEnvelopeJsonArray(blockInfo, blockNumber));
        return getSuccess(blockJson);
    }

    /** 解析区块包 */
    private JSONArray getEnvelopeJsonArray(BlockInfo blockInfo, long blockNumber) throws UnsupportedEncodingException, InvalidProtocolBufferException {
        JSONArray envelopeJsonArray = new JSONArray();
        for (BlockInfo.EnvelopeInfo info : blockInfo.getEnvelopeInfos()) {
            JSONObject envelopeJson = new JSONObject();
            envelopeJson.put("channelId", info.getChannelId());
            envelopeJson.put("transactionID", info.getTransactionID());
            envelopeJson.put("validationCode", info.getValidationCode());
            envelopeJson.put("timestamp", Utils.parseDateFormat(new Date(info.getTimestamp().getTime())));
            envelopeJson.put("type", info.getType());
            envelopeJson.put("createId", info.getCreator().getId());
            envelopeJson.put("createMSPID", info.getCreator().getMspid());
            envelopeJson.put("isValid", info.isValid());
            envelopeJson.put("nonce", Hex.encodeHexString(info.getNonce()));
            if (info.getType() == BlockInfo.EnvelopeType.TRANSACTION_ENVELOPE) {
                BlockInfo.TransactionEnvelopeInfo txeInfo = (BlockInfo.TransactionEnvelopeInfo) info;
                JSONObject transactionEnvelopeInfoJson = new JSONObject();
                int txCount = txeInfo.getTransactionActionInfoCount();
                transactionEnvelopeInfoJson.put("txCount", txCount);
                transactionEnvelopeInfoJson.put("isValid", txeInfo.isValid());
                transactionEnvelopeInfoJson.put("validationCode", txeInfo.getValidationCode());
                transactionEnvelopeInfoJson.put("transactionActionInfoArray", getTransactionActionInfoJsonArray(txeInfo, txCount));
                envelopeJson.put("transactionEnvelopeInfo", transactionEnvelopeInfoJson);
            }
            envelopeJsonArray.add(envelopeJson);
        }
        return envelopeJsonArray;
    }

    /** 解析交易请求集合 */
    private JSONArray getTransactionActionInfoJsonArray(BlockInfo.TransactionEnvelopeInfo txeInfo, int txCount) throws UnsupportedEncodingException, InvalidProtocolBufferException {
        JSONArray transactionActionInfoJsonArray = new JSONArray();
        for (int i = 0; i < txCount; i++) {
            BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo txInfo = txeInfo.getTransactionActionInfo(i);
            int endorsementsCount = txInfo.getEndorsementsCount();
            int chaincodeInputArgsCount = txInfo.getChaincodeInputArgsCount();
            JSONObject transactionActionInfoJson = new JSONObject();
            transactionActionInfoJson.put("responseStatus", txInfo.getResponseStatus());
            transactionActionInfoJson.put("responseMessageString", printableString(new String(txInfo.getResponseMessageBytes(), "UTF-8")));
            transactionActionInfoJson.put("endorsementsCount", endorsementsCount);
            transactionActionInfoJson.put("chaincodeInputArgsCount", chaincodeInputArgsCount);
            transactionActionInfoJson.put("status", txInfo.getProposalResponseStatus());
            transactionActionInfoJson.put("payload", printableString(new String(txInfo.getProposalResponsePayload(), "UTF-8")));


            transactionActionInfoJson.put("endorserInfoArray", getEndorserInfoJsonArray(txInfo, endorsementsCount));


            transactionActionInfoJson.put("argArray", getArgJSONArray(i, txInfo, chaincodeInputArgsCount));


            TxReadWriteSetInfo rwsetInfo = txInfo.getTxReadWriteSet();
            JSONObject rwsetInfoJson = new JSONObject();
            if (null != rwsetInfo) {
                int nsRWsetCount = rwsetInfo.getNsRwsetCount();
                rwsetInfoJson.put("nsRWsetCount", nsRWsetCount);
                rwsetInfoJson.put("nsRwsetInfoArray", getNsRwsetInfoJsonArray(rwsetInfo));
            }
            transactionActionInfoJson.put("rwsetInfo", rwsetInfoJson);
            transactionActionInfoJsonArray.add(transactionActionInfoJson);
        }
        return transactionActionInfoJsonArray;
    }

    /** 解析参数 */
    private JSONArray getArgJSONArray(int i, BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo txInfo, int chaincodeInputArgsCount) throws UnsupportedEncodingException {
        JSONArray argJsonArray = new JSONArray();
        for (int z = 0; z < chaincodeInputArgsCount; ++z) {
            argJsonArray.add(printableString(new String(txInfo.getChaincodeInputArgs(z), "UTF-8")));
        }
        return argJsonArray;
    }

    /** 解析背书信息 */
    private JSONArray getEndorserInfoJsonArray(BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo txInfo, int endorsementsCount) {
        JSONArray endorserInfoJsonArray = new JSONArray();
        for (int n = 0; n < endorsementsCount; ++n) {
            BlockInfo.EndorserInfo endorserInfo = txInfo.getEndorsementInfo(n);
            String signature = Hex.encodeHexString(endorserInfo.getSignature());
            String id = endorserInfo.getId();
            String mspId = endorserInfo.getMspid();
            JSONObject endorserInfoJson = new JSONObject();
            endorserInfoJson.put("signature", signature);
            endorserInfoJson.put("id", id);
            endorserInfoJson.put("mspId", mspId);

            endorserInfoJsonArray.add(endorserInfoJson);
        }
        return endorserInfoJsonArray;
    }

    /** 解析读写集集合 */
    private JSONArray getNsRwsetInfoJsonArray(TxReadWriteSetInfo rwsetInfo) throws InvalidProtocolBufferException, UnsupportedEncodingException {
        JSONArray nsRwsetInfoJsonArray = new JSONArray();
        for (TxReadWriteSetInfo.NsRwsetInfo nsRwsetInfo : rwsetInfo.getNsRwsetInfos()) {
            final String namespace = nsRwsetInfo.getNamespace();
            KvRwset.KVRWSet rws = nsRwsetInfo.getRwset();
            JSONObject nsRwsetInfoJson = new JSONObject();

            nsRwsetInfoJson.put("readSet", getReadSetJSONArray(rws, namespace));
            nsRwsetInfoJson.put("writeSet", getWriteSetJSONArray(rws, namespace));
            nsRwsetInfoJsonArray.add(nsRwsetInfoJson);
        }
        return nsRwsetInfoJsonArray;
    }

    /** 解析读集 */
    private JSONArray getReadSetJSONArray(KvRwset.KVRWSet rws, String namespace) {
        JSONArray readJsonArray = new JSONArray();
        int rs = -1;
        for (KvRwset.KVRead readList : rws.getReadsList()) {
            rs++;
            String key = readList.getKey();
            long readVersionBlockNum = readList.getVersion().getBlockNum();
            long readVersionTxNum = readList.getVersion().getTxNum();
            JSONObject readInfoJson = new JSONObject();
            readInfoJson.put("namespace", namespace);
            readInfoJson.put("readSetIndex", rs);
            readInfoJson.put("key", key);
            readInfoJson.put("readVersionBlockNum", readVersionBlockNum);
            readInfoJson.put("readVersionTxNum", readVersionTxNum);
            readInfoJson.put("chaincode_version", String.format("[%s : %s]", readVersionBlockNum, readVersionTxNum));
            readJsonArray.add(readInfoJson);
        }
        return readJsonArray;
    }

    /** 解析写集 */
    private JSONArray getWriteSetJSONArray(KvRwset.KVRWSet rws, String namespace) throws UnsupportedEncodingException {
        JSONArray writeJsonArray = new JSONArray();
        int rs = -1;
        for (KvRwset.KVWrite writeList : rws.getWritesList()) {
            rs++;
            String key = writeList.getKey();
            String valAsString = printableString(new String(writeList.getValue().toByteArray(), "UTF-8"));
            JSONObject writeInfoJson = new JSONObject();
            writeInfoJson.put("namespace", namespace);
            writeInfoJson.put("writeSetIndex", rs);
            writeInfoJson.put("key", key);
            writeInfoJson.put("value", valAsString);
            writeJsonArray.add(writeInfoJson);
        }
        return writeJsonArray;
    }

    private String printableString(final String string) {
        int maxLogStringLength = 64;
        if (string == null || string.length() == 0) {
            return string;
        }
        String ret = string.replaceAll("[^\\p{Print}]", "?");
        ret = ret.substring(0, Math.min(ret.length(), maxLogStringLength)) + (ret.length() > maxLogStringLength ? "..." : "");
        return ret;
    }
}
